#! /usr/bin/python
"""
 Module: readmeter.py

 Ver 0.8.0
 Original Release: 01/02/2009

 Rev 0.9.0; Last: 02/27/2013 - Previous: 02/26/2013 (AEM)
   - Update associated comments
   - Add more error checking

 Rev 0.9.2; Last: 03/11/2013 - Previous: 03/11/2013 (AEM)
   - Incorporate Plot DMM modules

 Rev 0.9.3-0; Last: 03/26/2013 - Previous: 03/25/2013 (AEM)
   - Mods to accommodate GUI (module readermain.py)
     This module can be executed via the terminal or GUI transparently.
     The IsTermial argument in the function main_session_mgr
     will direct the proper sequences.

 Command line utility to Loop and acquire meter readings from a
 Mastech MAS-345 digital multimeter.
 Print readings to stdout and a log file.
 This module can be executed stand alone from the terminal,
 or will be imported and exected from the GUI.
 Functionally, everything operate the same.

 This file is part of the DMM Reader project.

 Copyright (C) 2012-2013  Arnold E. Morgan

    DMM Reader is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""

# *** System Supplied Modules ***
import sys
import time
import datetime
import os
import inspect         # *** Debug ***

try:
   import wx
except ImportError:
   print "\nSorry: no implementation of wxPython can be found on your " \
         "system ('%s'). \nIf you intend to use the GUI, please install " \
         "wxPython as descriibed in the README.txt file.\n" \
         "If you are using this application at the command line, you may " \
         "safely ignore this message." % os.name

# *** Custom Modules ***
import serialportmgr
import cfginit

StopTheSession = False

def main_session_mgr(MainFrameObj, IsTerminal=False, PreInitValues=''):
   """ Initialize all meter reading and loop control
   attributes. Initiate the main reader loop.

   if a terminal execution, get values from the cfg file. If a GUI execution,
   cfg vales will be passed in.
   """

   # Technically the global declaration is not needed, but just in case I
   # forget and assign a value to this variable it should change
   # the module global version only.
   global StopTheSession
   #Debug = True
   Debug = False
   FailFlag = False
   WarningMssg = ""
   LogFileHd = None
   SerialPort = None

   # Check if this a terminal or GUI execution.
   if IsTerminal:
      InitValues = {}
      (FailFlag, InitValues) = get_cfg_init(IsTerminal)
   else:
      InitValues = PreInitValues

   if not FailFlag:
      (FailFlag, LogFileHd) = set_log_file(InitValues['LogFileName'], IsTerminal)

   if not FailFlag:
      (FailFlag, SerialPort) = set_serial_port(InitValues['Port'],
                                               InitValues['Baud'],
                                               InitValues['Timeout'],
                                               InitValues['Bits'], IsTerminal)

   # ********* Main read loop begins here *********
   if not FailFlag:
      StopDT = ''
      if 'EndTime' in InitValues.keys():
         if len(InitValues['EndTime']) == 5:
            BeginDT = datetime.datetime.now()
            StopDT = BeginDT.strftime("%Y-%m-%d " + InitValues['EndTime'][0:5] + ':00')

      CheckReadCnt = False
      if 'ReadCnt' in InitValues.keys():
         CheckReadCnt = True

      CurrentDT = ""
      if IsTerminal:
         print "Beginning %s second read loop..." % InitValues['ReadPeriod']

      try:
         ctr = 0
         done = False
         while not done:
            StdOutStr = ""
            LogOutStr = ""
            ctr += 1
            if SerialPort:
               (MeterVal, MeterUnit, StdOutStr, LogOutStr, FailFlag, WarningMssg) = serialportmgr.GetMeterReading(SerialPort,
                                                               InitValues['InputChars'])

               if FailFlag:
                  print "\nMeter session error: " + WarningMssg
                  done = True

            if not FailFlag:
               CurrentDT = datetime.datetime.now()
               FormattedDT = CurrentDT.strftime("%Y-%m-%d %H:%M:%S")

               if LogFileHd:
                  LogOutStr = "%5d %s %s" % (ctr, FormattedDT, LogOutStr)
                  LogFileHd.write(LogOutStr + "\n")
                  LogFileHd.flush()

               MssgOutStr = "%5d) %s; %s" % (ctr, FormattedDT, StdOutStr)
               if IsTerminal:
                  print MssgOutStr
               else:
                  send_insert_GUI_mssg(MainFrameObj, str(MeterVal), MeterUnit, MssgOutStr)

               # Check for max number of readings
               if CheckReadCnt:
                  if InitValues['ReadCnt'] > 0:
                     if ctr >= InitValues['ReadCnt']:
                        print "\nEnding session after %d requested readings" % InitValues['ReadCnt']
                        done = True
                        break

               # Check for max read time
               if len(StopDT) > 0:
                  if StopDT < FormattedDT:
                     print "\nEnding session after requested end time: %s " % StopDT
                     done = True
                     break
                     
               if int(InitValues['ReadPeriod']) > 0:
                  # Loop for the wait period or until the session is ended by a click
                  # of the "End Session" button, whichever occurs first.
                  BeginWait = time.time()
                  while BeginWait + int(InitValues['ReadPeriod']) > time.time():
                     if StopTheSession:
                        break

                  if StopTheSession:
                     done = True

            # *** End of GetMeterReading FailFlag check ***
         # *** End of while loop ***
      except KeyboardInterrupt:
         WarningMssg = "\nUser initiated keyboard interrupt: condition ok"
         # Cannot send exception mssgs to the GUI, it will hang forever.
         print WarningMssg
      except Exception, ex:
         WarningMssg = "\nCaptured Error: " + str(ex)
         print WarningMssg
      except:
         WarningMssg = "\nUnexpected Error: ", sys.exc_info()[0]
         print WarningMssg

      if SerialPort:
         WarningMssg = "\nClosing the serial port..."
         print WarningMssg
         SerialPort.close()
      
      if LogFileHd:
         WarningMssg = "\nClosing the log file..."
         print WarningMssg
         LogFileHd.close()
   # ********* Main read loop ends here *********

   WarningMssg = "\nThe End"
   print WarningMssg

   if StopTheSession:
      WarningMssg = "\nMeter session has been instructed to end\n"
      print WarningMssg

   if not IsTerminal and not StopTheSession:
      # If StopTheSession is True, then the session was stopped by clicking
      # the "End Session" button. In that case, the session GUI controls will
      # be re-enabled back in the calling module.
      # Alternative stop session events ocurr as the result of max number of 
      # readings or or end time was reached. In those cases, we need to
      # explicitly re-enable everything.
      restore_session_ctrls(MainFrameObj)
# *** End of main_session_mgr ***

def get_cfg_init(IsTerminal):
   """ If execution by the terminal, get config settings here. """

   RtnStat = False
   FailFlag = False
   RtnMssg = ""
   InitValues = {}
   (RtnStat, InitValues, RtnMssg) = cfginit.GetCfgSettings(IsTerminal)
   if not RtnStat:
      if IsTerminal:
         print "Config file read failed:\n " + RtnMssg
      FailFlag = True
   else:
      if IsTerminal:
         print "\nInitValues dictionary:"
         print "_" * 50
         keylist = InitValues.keys()
         keylist.sort()
         for keyval in keylist:
            print "%-25s   %-s" % (keyval, InitValues[keyval])

   return (FailFlag, InitValues)
   # ------------------------------------------------------

def set_log_file(LogName, IsTerminal):
   """ Create the log file.
   Return the file handle if successful.
  """

   LogFileHd = None
   RtnStat = False
   FailFlag = False
   Debug = False

   if IsTerminal:
      print "\nSetting up log file..."
   (RtnStat, LogFileHd) = cfginit.CreateLogFile(LogName, IsTerminal)
   if Debug and IsTerminal:
      print "RtnStat: %s; LogFileHd: %s" % (RtnStat, LogFileHd)

   if not RtnStat:
      if IsTerminal:
         print "Log file was not created; logging option is disabled"
      FailFlag = True

   return (FailFlag, LogFileHd)
   # ------------------------------------------------------

def set_serial_port(PortVal, BaudVal, TimeoutVal, BitsVal, IsTerminal):
   """ Set up the serial port """

   # *** DEBUG ***
   """
   print "\nModule: " + os.path.basename(__file__) +  \
         " -- function: " + inspect.stack()[0][3] + \
         " -- caller: " + inspect.stack()[1][3] + " -- line: " + \
          str(inspect.currentframe().f_lineno) + "\n"
   # *** DEDUG ***
   """
   FailFlag = False
   RtnStat = True
   WarningMssg = ""
   SerialPort = None

   if len(PortVal) > 0:
      if IsTerminal:
         print "Setting up serial port"
      (RtnStat, SerialPort, WarningMssg) = serialportmgr.SetSerialPort(PortVal,
                                                                       BaudVal,
                                                                       TimeoutVal,
                                                                       BitsVal)
      if not RtnStat:
         if IsTerminal:
            print WarningMssg
         FailFlag = True

   return (FailFlag, SerialPort)
   # ----------- End of function set_serial_port --------------------------

def send_insert_GUI_mssg(MainFrameObj, MeterVal='', MeterUnit='', Mssg=''):
   """ If using the GUI, update it with these messages.

   If this module is being executed at the terminal, this
   function is not used.
   """

   wx.MutexGuiEnter()
   MainFrameObj.insert_new_session_list_item(Mssg.rstrip('\r\n'))

   if len(MeterVal) > 0 or len(MeterUnit) > 0:
      MainFrameObj.update_lastest_measurement(MeterVal, MeterUnit)

   wx.MutexGuiLeave()
   # ---------------- End of function send_insert_GUI_ mssg -----------------

def restore_session_ctrls(MainFrameObj):
   """ Signal back to the GUI to re-enable all session
   controls.
   """

   wx.MutexGuiEnter()
   MainFrameObj.enable_session_action_ctrls()
   wx.MutexGuiLeave()
   # ---------------- End of function restore_session_ctrls -----------------


if __name__ == "__main__":
   main_session_mgr(None, IsTerminal=True, PreInitValues='')

